package ginutil

import (
	"net/http"
	"reflect"

	"gitee.com/xuyiping_admin/pkg/jsonpb"
	"gitee.com/xuyiping_admin/pkg/xerr"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/golang/protobuf/proto"
	"github.com/huandu/xstrings"
)

var camelQuery = &CamelQueryBinding{}

// BindQuery with query params
func BindQuery(c *gin.Context, obj interface{}) error {
	return c.ShouldBindWith(obj, camelQuery)
}

type CamelQueryBinding struct{}

func (*CamelQueryBinding) Name() string {
	return "camel_query"
}

func (*CamelQueryBinding) Bind(req *http.Request, obj interface{}) error {
	values := req.URL.Query()
	if err := mapFormByTag(obj, values, "json"); err != nil {
		return err
	}
	return binding.Validator.ValidateStruct(obj)
}

type camelFormSource map[string][]string

// TrySet tries to set a value by request's form source (like map[string][]string)
func (form camelFormSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
	return setByForm(value, field, form, xstrings.FirstRuneToLower(xstrings.ToCamelCase(tagValue)), opt)
}

// BindProtoMessage with proto message from json body
func BindProtoMessage(c *gin.Context, obj proto.Message) error {
	return c.ShouldBindWith(obj, protoMessageBindingFromBody)
}

var protoMessageBindingFromBody = &ProtoMessageBinding{}

type ProtoMessageBinding struct{}

func (*ProtoMessageBinding) Name() string {
	return "proto_message_binding"
}

func (*ProtoMessageBinding) Bind(req *http.Request, obj interface{}) error {
	pbMessage, ok := obj.(proto.Message)
	if !ok {
		return xerr.New("bind obj should be proto.Message")
	}

	if err := jsonpb.Unmarshal(req.Body, pbMessage); err != nil {
		return xerr.WithStack(err)
	}

	return binding.Validator.ValidateStruct(obj)
}
